package bosVM2.Engine.Frames;

import java.util.HashMap;
import java.util.Stack;

import bosVM2.Engine.ExecutionEngine;
import bosVM2.Engine.Heap;
import bosVM2.FaultHandling.HamCodeFaultHandler;
import bosVM2.meta.LookUp.Type;
import bosVM2.meta.LookUp.aritOp;
import bosVM2.meta.LookUp.ifOp;
import bosVM2.meta.LookUp.opCode;
import bosVM2.meta.Objects.BosMethod;
import bosVM2.meta.Objects.BosObject;

public class MethodFrame implements Frame {
	
	//Globals
	private Heap H = Heap.getH();
	private ExecutionEngine e;
	
	//Frame stuff
	private BosMethod m;
	private int pc;
	private HashMap<String,Object> lv;
	private Stack<Object> os;
	
	public MethodFrame(BosMethod m,Integer ref, ExecutionEngine e) 
	{
		this.m = new BosMethod(m);
		this.pc = 0;
		this.lv = new HashMap<String,Object>();
		lv.put("this", ref);
		this.os = new Stack<Object>();
		this.e = e;
	}
	
	public BosMethod getMethod() {
		return m;
	}
	
	public int getPc() {
		return pc;
		
	}
	
	public void setPc(int i)
	{
		pc = i;
	}
	
	public HashMap<String,Object> getlv() {
		return lv;
	}
	
	public Stack<Object> getOs() {
		return os;
	}

	public ExecutionEngine getEngine()
	{
		return e;
	}
	
	public MethodFrame getMethodFrame()
	{
		return this;
	}
	@Override
	public Object executeFrame() throws Exception {
		
		while(!(m.instructionAt(pc) == null))
		{	

			getMethodFrame();
			Object[] o = m.instructionAt(pc);
			opCode op = opCode.getOpcode((Integer)o[0]);
			switch(op)
			{
			
			//CORE
			case NOP: ++pc; break;
			case POP: doPop((Integer) o[1]); break;
			case PUSH: doPush(o[2]); break;
			case GOTO: pc = (Integer) o[1]; break;
			case ARIT: doArit((aritOp)o[1]); break;
			case IF:  doIf((ifOp) o[1], (Integer) o[2]); break;
			case LOAD: doLoad((Type) o[1],(String) o[2]); break;
			case STORE: doStore((Type) o[1], (String) o[2]); break;
			//Methods
			case INVOKEVIRTUAL: doInvokeVirtual(o); break;
			case RETURN: return os.pop();
			case NEW: doNew((String) o[1]); break;
			case PUTFIELD: doPutField(o); break;
			case GETFIELD: doGetField(o); break;
			case THROW: System.err.println("not yet implemented"); return null;
			case UNREC: throw new Exception("Syntax error");
			}
		}
		return null; //FIXME
	}

	private void doGetField(Object[] o) {
		os.push(H.get((Integer) os.pop()).getField((String) o[1]));
		++pc;
	}

	private void doPutField(Object[] o) {
		H.get((Integer) os.pop()).putfield((String) o[1], os.pop());
		++pc;
	}

	private void doNew(String clss) {
		int i = H.Allocate(e.getMeta().getMetaClass(clss));
		os.push(i);	
		++pc;
	}

	private void doInvokeVirtual(Object[] o) throws Exception { //TODO
		String clss = ((String) o[1]).substring(0,((String) o[1]).lastIndexOf("."));
		String meth =((String) o[1]).substring(((String) o[1]).lastIndexOf(".")+1);
		BosMethod im = e.getMeta().getMetaClass(clss).getMethod(meth);
		int ref = (Integer) os.pop(); //Class
		BosObject obj = H.get(ref);
		MethodFrame frame = new MethodFrame(im,ref,e);
		for(String s: im.getArgs())
		{
			frame.lv.put(s, os.pop());
		}
		if(o[1] == Type.VOID) { frame.executeFrame(); }
		else { os.push(frame.executeFrame());}
		++pc;	
	}

	private void doStore(Type type, String addr) {
		lv.put(addr, os.pop());	
		++pc;
	}

	private void doLoad(Type type, String addr) {
		os.push(lv.get(addr));
		++pc;	
	}

	private void doIf(ifOp ifOp, Integer i) throws Exception {
		switch(ifOp)
		{
		case EQUALS:
			if(((Integer) os.pop()).equals((Integer) os.pop())) { pc = i;}
			else {++pc;} break;
		case GRTH:
			if((Integer) os.pop() > (Integer) os.pop()) { pc = i;}
			else {++pc;} break;
		case LSTH:
			if((Integer) os.pop()  < (Integer)os.pop()) { pc = i;}
			else {++pc;} break;
		case NEQUALS:
			if((Integer)os.pop() != (Integer)os.pop()) { pc = i;}
			else {++pc;} break;
		default: System.err.println("Error detected in If operation");
			throw new Exception("ifCode fault");
		}	
	}

	private void doArit(aritOp o) throws Exception {
		switch(o)
		{
			case ADD: os.push((Integer)os.pop() + (Integer)os.pop());
			++pc;
				break;
			case DIV: os.push((Integer)os.pop() / (Integer)os.pop());
			++pc;
				break;
			case MUL: os.push((Integer)os.pop() * (Integer)os.pop());
			++pc;
				break;
			case SUB: os.push((Integer)os.pop() - (Integer)os.pop());
			++pc;
				break;
			default: System.err.println("Error detected in Arit Operation");
					throw new Exception("Arit code fault");
		}		
	}

	private void doPush(Object v) {
		os.push(v);
		++pc;
	}

	private void doPop(int i) {
		for(int j = 0; j < i; j++)
		{ os.pop();}
		++pc;
	}
}
